package com.declum.peyar.handler;

import org.apache.log4j.Logger;
import org.apache.mina.common.IdleStatus;
import org.apache.mina.common.IoHandlerAdapter;
import org.apache.mina.common.IoSession;
import org.apache.mina.common.TransportType;
import org.apache.mina.transport.socket.nio.SocketSessionConfig;

import com.declum.peyar.db.PeyarDB;

/**
 * This class implement the handler for the Peyar Server which maintains the
 * naming service in the network.
 * 
 * For more detailed information, please read the comments above the methods and
 * fields
 * 
 * @author clement
 */
public class PeyarHandler extends IoHandlerAdapter {

	/**
	 * Constants for describing the Commands
	 */
	protected static final String BIND = "bind";
	protected static final String LOOKUP = "lookup";
	protected static final String UNBIND = "unbind";

	/**
	 * Logger object for this Class and its children
	 */
	protected Logger logger;
	/**
	 * Object for {@link PeyarDB}
	 */
	PeyarDB peyarDB;

	/**
	 * Constructor that initialize the PeyarDB and Logger
	 */
	public PeyarHandler() {
		this.peyarDB = new PeyarDB();
		this.logger = Logger.getLogger(PeyarHandler.class);
	}

	/**
	 * If any exception occurred while handling the Client's request, this
	 * method will be called. Here we are just closing the connection. In
	 * future, we can add some processing there
	 */
	@Override
	public void exceptionCaught(IoSession session, Throwable t)
			throws Exception {
		/**
		 * Print to CONSOLE
		 */
		t.printStackTrace();

		/**
		 * Close client session, as some exception occurred
		 */
		session.close();
	}

	/**
	 * When ever a line is received, this method is called, as we are using Line
	 * Encoder/Decorder.
	 * 
	 * So for receiving of each line, we are just paring them as header/value.
	 * Once all required headers are received(will get his from RequestHeader
	 * object's isComplete() method), we start processing the requests
	 * 
	 * There are three kinds of Commands identified. They are BIND, LOOKUP, and
	 * UNBIND. Based on these values which got from the Client's request, we
	 * start processing.
	 * 
	 * ResponseHeader is used to send the response to the Client
	 */
	@Override
	public void messageReceived(IoSession session, Object msg) throws Exception {
		try {
			/**
			 * We cannot maintain a object state if we are creating it inside
			 * this method. Because, this method will be triggered when ever
			 * client is sending the data. So Usually we create a State
			 * object(RequestHeader) in the SessionCreate method and we will be
			 * accessing it whenever we need through out the session for the
			 * current client
			 */
			RequestHeader header = (RequestHeader) session
					.getAttribute("header");
			try {
				header.parse(msg.toString());
			} catch (Exception e) {
				session.write(e.getMessage());
				session.close();
			}

			if (header.isComplete()) {

				String type = header.getCommand().toLowerCase();
				ResponseHeader response = new ResponseHeader();

				if (type.equals(PeyarHandler.BIND)) {
					this.logger.debug("Bind request for " + header.getAddress()
							+ " with " + header.getServiceName());
					boolean bindFlag = this.peyarDB.bind(header
							.getServiceName(), header.getAddress());
					if (bindFlag) {
						response.setStatus("success");
					} else {
						response.setStatus("failed");
						response.setReason("address alread bound");
					}
				} else if (type.equals(PeyarHandler.LOOKUP)) {
					this.logger.debug("Lookup request for "
							+ header.getServiceName());
					String[] lookupAddresses = this.peyarDB.lookup(header
							.getServiceName());
					if (lookupAddresses != null) {
						String value = "";
						response.setStatus("success");
						for (String address : lookupAddresses) {
							value += " " + address;
						}
						response.setHeader("address", value);
					} else {
						response.setStatus("failed");
						response.setReason("service has no identifier");
					}
				} else if (type.equals(PeyarHandler.UNBIND)) {
					this.logger.debug("Unbind request for "
							+ header.getAddress() + " with "
							+ header.getServiceName());
					this.peyarDB.unbind(header.getServiceName(), header
							.getAddress());
					response.setStatus("success");
				} else {
					/**
					 * If none of the above commends satisfied for the request,
					 * then just say good bye
					 */
					response.setStatus("failed");
					response.setReason("Operation not supported");
					this.logger.debug("Operation '" + type + "' not supported");
				}

				session.write(response.toString());
				session.close();
			}
		} catch (Exception e) {
			/**
			 * Something went terribly wrong
			 */
			e.printStackTrace();
			this.logger.error(e.getMessage());
			session.write(e.getMessage());
		}
	}

	/**
	 * This method will be called once the client is accepted. It does the
	 * object creation and setting configuration information for this client
	 * session
	 */
	@Override
	public void sessionCreated(IoSession session) throws Exception {
		this.logger.debug("Session created");
		/**
		 * Here we are creating the object of RequestHeader. Because it has to
		 * maintains the state between method calls.
		 */
		session.setAttribute("header", new RequestHeader());

		/**
		 * I am really not knowing why this is set here. But sure one day i will
		 * understand. :-)
		 */
		if (session.getTransportType() == TransportType.SOCKET) {
			((SocketSessionConfig) session.getConfig())
					.setReceiveBufferSize(2048);
		}

		/**
		 * Set idle time
		 */
		session.setIdleTime(IdleStatus.BOTH_IDLE, 10);
	}
}
